#!/usr/bin/env python2.7
# ----------------------------------------------------------------------------
# "If it's stupid, but it works, then it's not stupid."
# -- The official motto of the QRPBBB project.

import sys, os, time, zlib, re, subprocess
import site_config as cfg
import qrpbbb_lib as lib

###

(cntsends, cnttotpackets, cntwants, cntidents, cntsendsgen) = (0,0,0,0,0)
(cntannounces, cntmsgign, cntheard, cntwants, cntpackets) = (0, 0, 0, 0, 0)
(stationsheard, lastinfo) = ([], "")

print "\nqrpbbb_process.py executing at:", time.ctime(),"\n"

msgidsdb = lib.msgids_scan()

# Main command-finding-processing loop
#
# make a (usually priority-sorted) list of received TNC2MON-encoded packets,
# check if they're for QRPBBB, they're not echos of this station from a
# digipeater, find out the command and payload, then process the command.

for file in sorted (os.listdir (cfg.KISS_IN)):
   fn = os.path.join (cfg.KISS_IN, file)
   fp = open(fn, 'rb')
   line = fp.readline().rstrip("\n")
   fp.close()
   cntpackets += 1

   packettime = time.localtime (os.path.getmtime (fn))
   print "Removing", cfg.KISS_IN+"/"+file+"\r",
   sys.stdout.flush()
   time.sleep (0.03)             # eh..
   os.remove (os.path.join(cfg.KISS_IN, file))

   if ">" + cfg.APPDEST not in line:
      continue

   # strip any kiss channel indicator like "[0] "
   line = re.sub("^\[[ 0-9:]+\] ", "", line)

   if re.match (cfg.CALLSIGN + ">" + cfg.APPDEST + ".*:{{Q", line, re.IGNORECASE):
      # skip echoed packets from this station
      continue

   # todo: cfg.BADCALLS check here maybe..

   (source, info) = line.split(':',1)

   if info == lastinfo:
      # another echoed packet
      continue
   lastinfo = info

   if "{{Q" not in info[0:3]:
      continue

   command = info[3:4]
   if info[4] != ':':
      continue

   print "\nFound useful packet:", line[:56]

   payload = info[5:]
   (call, path) = source.split ('>',1)
   if call not in stationsheard:
      stationsheard.append (call.upper())

   route = []
   for i in path.split(',')[1:]:
      if "WIDE" in i:
         continue
      route.insert(0,i.rstrip('*'))
   ROUTEBACK = ",".join(route)

   # ------------------------------------------------------------------------
   # "Command"
   #
   # {{QC:<payload>

   if command == 'C':
      if len(payload.split(' ')) == 1:
         (cmd, arg) = (payload, "")
      else:
         (cmd, arg) = payload.split(' ',1)

      # I would be happy if QRPBBB had a user who knew how to inject shellcode..
      if cmd == "uhave":
         subprocess.call ([os.path.join(cfg.QRPBBBHOME,"qrpbbb_ihave.py"), arg])

      if cmd == "uwant":
         # execute _iwant
         subprocess.call ([os.path.join(cfg.QRPBBBHOME,"qrpbbb_iwant.py"), arg])

      if cmd == "ident":
         # The new I:dentify command
         lib.packetout (
            "{}>{},{}:{{{{Qi:{}".format(cfg.CALLSIGN, cfg.APPDEST,
                                        ROUTEBACK, cfg.IDENT), priority=0)
         cntidents += 1

      if cmd == "uheard":
         # replies with a list from the Stations file
         all = lib.fetch_message (cfg.STATIONLOG) # naughty - only be used for posts
         # find the latest mention of a station
         # go through file bottom to top, only noting the latest mention of a site
         # bail out when the line is long enough (~7 stations)
         st = {}
         s = ""
         for l in sorted (all.keys(), reverse=True):
            (ut, date, clock, station, j) = all[l].split(' ',4)
            if st.get(station) == None and len(s) < 128:
               s += station + " " + ut + ","
               st[station] = ut

         lib.packetout (cfg.APRSPREAM + "h:" + s.rstrip(','), priority=0)
         cntheard += 1

   # ------------------------------------------------------------------------
   # "Introduce" & "identify". The first is a site wanting to know who else
   # can receive their messages. The second is what other sites have to say
   # about themselves.
   # I: could provide a payload in future for more specific requests.
   # ...although I think I'll manage that through C:
   #
   # {{QI:

   if command == 'I':
      # for now, let's try to be as visible as possible
      # the initial I command packet would have DISTROMAX as well
      lib.packetout (cfg.MAXPREAMB + "i:" + cfg.IDENT, priority=0)
      cntidents += 1

   if command == 'i':
      # response from an Identify command
      # use the time from the packet's arrival
      s = time.strftime ("%s %F %T ", packettime) + call + " " + payload + "\n"
      foo = open (cfg.STATIONLOG, mode='a')
      foo.write (s)
      foo.close()
      print "    Station identifying:", s
      cntidents += 1

   # ------------------------------------------------------------------------
   # "Want" - another site is requesting message content
   #
   # {{QW:msgid,linespec:msgid,linespec[...]

   if command == 'W':
      # there can be multiple requests of <msgid>,<linespec> in a line
      i = 0
      while i < len(payload):
         j = i+9
         while j < len(payload) and payload[j] != ':':
            j = j + 1
         msgid = payload[i:i+8]
         ls = payload[i+9:j]
         i = j + 1
         lib.qrpbbb_request (msgidsdb, msgid, ls)
         cntwants += 1

   # ------------------------------------------------------------------------
   # "Sending" - another site is broadcasting message content
   #
   # {{QS:<msgid>:<linespec>:<content>

   if command == 'S':
      # parse the fields
      j = 9
      while j < len(payload) and payload[j] != ':':
         j = j + 1
      msgid = payload[0:8]
      linelist = payload[9:j]  # can now be a linespec
      text = payload[j+1:]

      # check if the msgid is for a problem article
      if lib.badmsg_check (msgid):
         print "    Don't want to see this again - ignoring"
         cntmsgign += 1
         continue

      # if msgid isn't in QUEUE_IN, it's new! create!
      f = msgidsdb.get (msgid)
      if f == None:
         message = {}
      else:
         if cfg.NEWSSPOOL in f:
            print "    Already in newspool - ignoring"
            continue
         else:
            message = lib.fetch_message (f)

      (ll, j) = lib.expand_linespec (linelist)
      doupdate = 0
      for li in ll:
         # yes, Python has evaluation order in conditionals
         if message.get (li) == None or isinstance (message[li], int):
            print "    Adding line:",li," text:",text
            message[li] = text
            doupdate = 1

      # update the on-disk copy if anything changed
      if doupdate:
         lib.update_message (msgid, message)
         msgidsdb[msgid] = os.path.join (cfg.QUEUE_IN, msgid)
      else:
         print "    nothing new"

      cntsends += 1

   # ------------------------------------------------------------------------
   # "Announce" - other sites letting the network know what it has, with basic
   # metadata (lines in the message, distribution). Prepare for a new message
   # if Anything not known about appears.
   #
   # {{QA:msgid,length:msgid,length[,distribution]:...

   if command == 'A':
      i = 0
      while i < len(payload):
         j = i+9
         while j < len(payload) and payload[j] != ':':
            j = j + 1
         cntannounces += 1

         if payload[i:j].count(',') == 1:
            # no distribution mentioned - defaults to qrpbbb
            (msgid, msglen) = payload[i:j].split(',')
            distro = "qrpbbb"
            print "  msgid=", msgid, "lines=", msglen
         else:
            (msgid, msglen, distro) = payload[i:j].split(',')
            print "  msgid=", msgid, "lines=", msglen, "distro=", distro

         i = j + 1

         if distro not in cfg.ACCEPTDISTRO:
            print "   Unwanted distribution, skipping."
         else:
            # test if msgid is already in NEWSSPOOL
            f = msgidsdb.get (msgid)
            if f == None:
               message = {}
            else:
               if lib.badmsg_check (msgid):
                  print "    Ignoring msgid",msgid
                  cntmsgign += 1
                  continue

               if cfg.NEWSSPOOL in f:
                  print "    Already in newspool - ignoring"
                  continue
               else:
                  message = lib.fetch_message (f)

            cr = []
            # if length != msglen, pad and update file
            for li in range (1,int(msglen)+1):
               # existing blank lines were being overwritten as -1
               if message.get (li) == None:
                  message[li] = -1
                  cr.append (li)

            if len(cr) > 0:
               print "    Creating lines:", lib.create_linespec (cr)
               lib.update_message (msgid, message)
               msgidsdb[msgid] = os.path.join (cfg.QUEUE_IN, msgid)
            else:
               print "    nothing new"

   # ------------------------------------------------------------------------

   print

#
#
#

print "\n\nPackets processed:", cntpackets
if len(stationsheard):
   print "Stations heard:   ", ' '.join(sorted (stationsheard))
if (cntsends):
   print "Send cmds seen:   ", cntsends
if (cntwants):
   print "Want requests:    ", cntwants
if (cntsendsgen):
   print "Sends generated:  ", cntsendsgen
if (cntidents):
   print "Ident req/lists:  ", cntidents
if (cntwants):
   print "Want requests:    ", cntwants
if (cntheard):
   print "Heard requests:   ", cntheard
if (cntannounces):
   print "Announcements:    ", cntannounces
if (cntmsgign):
   print "Messages ignored: ", cntmsgign
if (cnttotpackets):
   print "Packets generated:", cnttotpackets

#
# ----------------------------------------------------------------------------
#
